/* Create arrows that connect boxes */

#include <algorithm>
#include <cmath>

#include <cairomm/context.h>
#include <cairomm/refptr.h>

#include <gtkmm/widget.h>

#include <memchart/types.hpp>

#include "chart_box.hpp"
#include "vector2.hpp"

#include "memory_chart.hpp"

namespace memchart
{
	using std::abs;
	using std::sin;
	using std::cos;
	using std::atan2;

	static const double pi = 4 * std::atan(1.0);

	static const double arrow_head_size = 10;
	static const double arrow_head_angle = pi / 6;

	static const double epsilon = 1e-5;

	static void draw_arrow (Cairo::RefPtr<Cairo::Context> & cr,
		double x1, double y1, double x2, double y2)
	{
		cr->move_to(x1, y1);
		cr->line_to(x2, y2);
		cr->stroke();

		double angle = atan2(y2 - y1, x2 - x1);
		cr->move_to(x2, y2);
		cr->line_to(
			x2 - arrow_head_size * cos(angle - arrow_head_angle),
			y2 - arrow_head_size * sin(angle - arrow_head_angle));
		cr->line_to(
			x2 - arrow_head_size * cos(angle + arrow_head_angle),
			y2 - arrow_head_size * sin(angle + arrow_head_angle));
		cr->close_path();
		cr->fill();
	}

	static inline double sign (double x)
	{
		return x > 0 ? 1 : -1;
	}

	static vector2 shift_endpoint (vector2 size, const vector2 & v)
	{
		size /= 2;

		if (abs(v.x) < epsilon)
			return vector2(0, size.y * sign(v.y));
		if (abs(v.y) < epsilon)
			return vector2(size.x * sign(v.x), 0);

		vector2 res1;
		res1.y = size.y * sign(v.y);
		res1.x = res1.y * v.x / v.y;

		vector2 res2;
		res2.x = size.x * sign(v.x);
		res2.y = res2.x * v.y / v.x;

		return res1.length2() < res2.length2() ? res1 : res2;
	}

	void MemoryChart::draw_arrows (Cairo::RefPtr<Cairo::Context> & cr)
	{
		for (box_iter v = boxes.begin(); v != boxes.end(); ++v)
		{
			chart_box & box = v->second;
			Gtk::Allocation a = box.widget->get_allocation();

			for (edge_iter e = box.edges.begin(); e != box.edges.end(); ++e)
			{
				box_iter v2 = boxes.find(*e);
				if (v2 != boxes.end())
				{
					Gtk::Allocation b = v2->second.widget->get_allocation();

					double x1 = a.get_x() + a.get_width() / 2.0,
						y1 = a.get_y() + a.get_height() / 2.0,
						x2 = b.get_x() + b.get_width() / 2.0,
						y2 = b.get_y() + b.get_height() / 2.0;

					vector2 as = shift_endpoint(
						vector2(a.get_width(), a.get_height()),
						vector2(x2 - x1, y2 - y1));
					vector2 bs = shift_endpoint(
						vector2(b.get_width(), b.get_height()),
						vector2(x1 - x2, y1 - y2));

					draw_arrow (cr, x1 + as.x, y1 + as.y, x2 + bs.x, y2 + bs.y);
				}
			}
		}

		cr->save();
		cr->set_source_rgba(1, 1, 1, 0.75);
		for (box_iter v = boxes.begin(); v != boxes.end(); ++v)
		{
			Gtk::Allocation a = v->second.widget->get_allocation();
			cr->rectangle(a.get_x(), a.get_y(), a.get_width(), a.get_height());
			cr->fill();
		}
		cr->restore();
	}
}
